Passed
Pull Request — master (#25)
by
unknown
02:39
created

DrawHelpers.ts ➔ createMarkers   A

Complexity

Conditions 1

Size

Total Lines 15
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 15
dl 0
loc 15
rs 9.65
c 0
b 0
f 0
cc 1
1
import { changeZoom, getLabelTextDimensions, getNodeDimensions } from './GraphHelpers';
2
import { DependencyLink, DependencyNode, NodeSelection } from '../../components/types';
3
import { forceCenter, forceCollide, forceLink, forceSimulation, forceY } from 'd3-force';
4
import { select, Selection } from 'd3-selection';
5
import { zoom } from 'd3-zoom';
6
import { BASE_FONT_SIZE, ElementColors, LabelColors, MAXIMUM_ZOOM_SCALE, MINIMUM_ZOOM_SCALE, ElementIds, TextColors } from '../AppConsts';
7
import { selectById } from './Selectors';
8
9
export function createLinkElements(zoomLayer: NodeSelection<SVGGElement>, links: DependencyLink[]) {
10
    return zoomLayer
11
        .append('g')
12
        .attr('id', ElementIds.LINKS)
13
        .lower()
14
        .selectAll<HTMLElement, DependencyLink>('line.link')
15
        .data(links)
16
        .enter()
17
        .append<SVGPathElement>('svg:path')
18
        .attr('class', 'link')
19
        .attr('marker-end', 'url(#provider)')
20
        .style('stroke-width', 1);
21
}
22
23
export function createLabels(labelNodesGroup: NodeSelection<SVGGElement>, nodes: DependencyNode[]) {
24
    labelNodesGroup
25
        .selectAll('g')
26
        .data(nodes)
27
        .append<SVGPathElement>('svg:path')
28
        .attr('class', 'label')
29
        .attr('fill', LabelColors.DEFAULT)
30
        .attr('d', createLabelPath);
31
}
32
33
export function createTextElements(labelNodesGroup: NodeSelection<SVGGElement>, nodes: DependencyNode[]) {
34
    return labelNodesGroup
35
        .selectAll<HTMLElement, DependencyNode>('g#labels')
36
        .data(nodes)
37
        .enter()
38
        .append<SVGGElement>('g')
39
        .attr('cursor', 'pointer')
40
        .append('text')
41
        .attr('text-anchor', 'middle')
42
        .attr('font-size', BASE_FONT_SIZE)
43
        .attr('fill', TextColors.DEFAULT)
44
        .text(d => d.name);
45
}
46
47
export function createMarkers(svgContainer: any): void {
48
    svgContainer
49
        .append('svg:defs')
50
        .append('svg:marker')
51
        .attr('id', 'provider')
52
        .attr('viewBox', '-5 -5 40 10')
53
        .attr('refX', 15)
54
        .attr('refY', 0)
55
        .attr('markerWidth', 40)
56
        .attr('markerHeight', 40)
57
        .attr('orient', 'auto')
58
        .append('svg:path')
59
        .attr('d', 'M0,-5L20,0L0,5,q10 -5,0 -10')
60
        .attr('fill', '#dcdee0');
61
}
62
63
export function createSimulation(nodes: DependencyNode[], links: DependencyLink[], width: number, height: number) {
64
    return forceSimulation(nodes)
65
        .force(
66
            'dependency',
67
            forceLink<DependencyNode, DependencyLink>(links)
68
                .distance(180)
69
                .id((node: DependencyNode) => node.name)
70
        )
71
        .force('center', forceCenter(width / 2, height / 2))
72
        .force('y', forceY(0.5))
73
        .force('collide', forceCollide(140))
74
        .force('nodeCollide', forceCollide(140));
75
}
76
77
export function createSVGContainer(width: number, height: number) {
78
    return selectById(ElementIds.OVERVIEW_CONTAINER_DIV)
79
        .append('svg')
80
        .attr('id', ElementIds.OVERVIEW_CONTAINER)
81
        .attr('preserveAspectRatio', 'xMidYMid meet')
82
        .attr('viewBox', `0 0 ${width} ${height}`)
83
        .attr('width', width)
84
        .attr('height', height);
85
}
86
87
export function createZoom(svgContainer: NodeSelection<SVGSVGElement>, selector: ElementIds.OVERVIEW_ZOOM | ElementIds.DETAILS_ZOOM) {
88
    const zoomLayer = svgContainer.append('g').attr('id', selector);
89
90
    svgContainer
91
        .call(
92
            zoom<SVGSVGElement, DependencyNode>()
93
                .scaleExtent([MINIMUM_ZOOM_SCALE, MAXIMUM_ZOOM_SCALE])
94
                .on(`zoom`, changeZoom(selector))
95
        )
96
        .on('dblclick.zoom', null);
97
98
    return zoomLayer;
99
}
100
101
export function createLinkPath(this: Element, link: DependencyLink): void {
102
    if (!link.source.x || !link.source.y || !link.target.x || !link.target.y) {
103
        return;
104
    }
105
    console.log(link);
106
107
    const xDiff = link.source.x - link.target.x;
108
    const yDiff = link.source.y - link.target.y;
109
110
    const isSourceOnTheLeft = xDiff < 0;
111
    const isSourceBelowTarget = yDiff > 0;
112
113
    const angleInRadians = Math.abs(Math.atan(yDiff / xDiff));
114
    const cosinus = Math.cos(angleInRadians);
115
    const sinus = Math.sin(angleInRadians);
116
117
    if (!link.source.width) {
118
        link.source.width = getNodeDimensions(link.source).width;
119
    }
120
    if (!link.target.width) {
121
        link.target.width = getNodeDimensions(link.target).width;
122
    }
123
124
    const sourceOffsetXLeft = -(20 + (link.source.width || 0) / 2) * cosinus;
125
    const targetOffsetXLeft = -(20 + (link.target.width || 0) / 2) * cosinus;
126
    const offsetY = 50 * sinus;
127
    const offsetYBelow = -offsetY - 5;
128
129
    const sourceNewX = isSourceOnTheLeft ? (link.source.width / 2 + 20) * cosinus : sourceOffsetXLeft;
130
    const sourceNewY = isSourceBelowTarget ? offsetYBelow : offsetY;
131
132
    const targetNewX = isSourceOnTheLeft ? targetOffsetXLeft : (link.target.width / 2 + 20) * cosinus;
133
    const targetNewY = isSourceBelowTarget ? offsetY : offsetYBelow;
134
135
    select<Element, DependencyLink>(this)
136
        .attr(
137
            'd',
138
            'M' +
139
                (link.source.x + sourceNewX) +
140
                ',' +
141
                (link.source.y + sourceNewY) +
142
                ', ' +
143
                (link.target.x + targetNewX) +
144
                ',' +
145
                (link.target.y + targetNewY)
146
        )
147
        .attr('stroke', LabelColors.DEFAULT);
148
}
149
150
export function createLabelPath(this: Node, node: DependencyNode) {
151
    const labelTextDimensions = getLabelTextDimensions(this);
152
153
    if (!labelTextDimensions) {
154
        return '';
155
    }
156
157
    const labelTextWidth = labelTextDimensions.width;
158
159
    const { isConsumer, isProvider } = node;
160
161
    const xOffset = -labelTextWidth / 2 + 4.5;
162
163
    if (isConsumer && isProvider) {
164
        return (
165
            'M' +
166
            xOffset +
167
            ',35l9.37,14.59L' +
168
            xOffset +
169
            ',64.18h' +
170
            (labelTextWidth + 45) +
171
            'l9-14.59L' +
172
            (labelTextWidth / 2 + 49.5) +
173
            ',35H' +
174
            xOffset +
175
            'z'
176
        );
177
    }
178
179
    if (isProvider) {
180
        return 'M' + (labelTextWidth / 2 + 49.5) + ',35H' + xOffset + 'l9.37,14.59L' + xOffset + ',64.18h' + (labelTextWidth + 45);
181
    }
182
183
    if (isConsumer) {
184
        return 'M' + xOffset + ',64.18h' + (labelTextWidth + 45) + 'l9.42-14.59L' + (labelTextWidth / 2 + 49.5) + ',35H' + xOffset + '';
185
    }
186
187
    return 'M' + xOffset + ',64.18h' + (labelTextWidth + 55) + 'L' + (labelTextWidth / 2 + 59.5) + ',35H' + xOffset;
188
}
189
190
export function createHighlightBackground(
191
    svgContainer: NodeSelection<SVGGElement>
192
): Selection<SVGRectElement, DependencyNode, Element, HTMLElement> {
193
    return svgContainer
194
        .append('rect')
195
        .attr('id', ElementIds.HIGHLIGHT_BACKGROUND)
196
        .attr('width', 0)
197
        .attr('height', 0)
198
        .attr('x', 0)
199
        .attr('y', 0)
200
        .attr('rx', 5)
201
        .attr('ry', 5)
202
        .attr('fill', ElementColors.HIGHLIGHT_BACKGROUND)
203
        .style('opacity', 0);
204
}
205
206
export function createDetailsButton(svgContainer: NodeSelection<SVGGElement>) {
207
    const detailsButtonWrapper = svgContainer
208
        .append('g')
209
        .attr('id', ElementIds.DETAILS_BUTTON)
210
        .attr('cursor', 'pointer');
211
    detailsButtonWrapper
212
        .append('rect')
213
        .style('opacity', 0)
214
        .attr('fill', ElementColors.BUTTON);
215
    detailsButtonWrapper
216
        .append('text')
217
        .style('opacity', 0)
218
        .style('text-anchor', 'middle')
219
        .attr('fill', TextColors.HIGHLIGHTED)
220
        .text('Details');
221
    return detailsButtonWrapper;
222
}
223